# -*- coding: utf-8 -*-

"""
对缓存封装，便于使用
"""

import json
import time
import functools
import logging
import hashlib
from contextlib import contextmanager

logger = logging.getLogger(__name__)


class RedisCacheProxy(object):

    def __init__(self, cache=None):
        self._client = cache

    @contextmanager
    def with_cache(self, key, ex=30, is_json=False, is_update=True, is_except=False):
        """
        @desc 将SimpleCache封装成上下文管理器，便于使用
        """
        s_cache = self.simple_cache(key, ex=ex, is_json=is_json, is_update=is_update, is_except=is_except)
        try:
            s_cache.data = s_cache.get()
            if s_cache.data:
                s_cache.update = False
            yield s_cache
        finally:
            if s_cache.update and s_cache.data:
                s_cache.set()

    def simple_cache(self, key, ex, is_json=False, is_update=True, is_except=False):
        return SimpleCache(key, ex=ex, cache=self._client, is_json=is_json, is_update=is_update, is_except=is_except)

    @contextmanager
    def with_lock(self, key, expire=30):
        """
        锁的with封装
        """
        try:
            t = self.acquire_lock(key, expire)
            yield t
        finally:
            self.release_lock(key)

    def check_lock(self, key):
        """
        检查当前KEY是否有锁
        """
        key = 'lock:%s' % key
        if self._client.get(key):
            self.locked = True
        else:
            self.locked = False
        return self.locked

    def acquire_lock(self, key, expire=30):
        """
        为当前KEY加锁, 默认30秒自动解锁
        """
        key = 'lock:%s' % key
        while 1:
            get_stored = self._client.get(key)
            if get_stored:
                time.sleep(0.03)
            else:
                if self._client.setnx(key, 1):
                    self._client.expire(key, expire)
                    self.locked = True
                    return self.locked

    def release_lock(self, key):
        """
        释放当前KEY的锁
        """
        key = 'lock:%s' % key
        self._client.delete(key)

    def get(self, key):
        data = self._client.get(key)
        if data:
            try:
                data = json.loads(data)
            except:
                pass
        return data

    def set(self, key, value, ex=10):
        try:
            value = json.dumps(value)
        except:
            pass
        return self._client.set(key, value, ex=ex)

    def get_many(self, keys):
        assert isinstance(keys, list)
        return self._client.mget(*keys)

    def set_many(self, data, ex=10):
        assert isinstance(data, dict)
        return self._client.mset(data, ex=int(ex))

    def delete(self, key):
        return self._client.delete(key)

    @property
    def client(self):
        return self._client

    def delete_many(self, keys):
        assert isinstance(keys, list)
        return self._client.delete(*keys)

    def exists(self, key):
        return self._client.exists(key)

    def hget(self, key, field):
        return self._client.hget(key, field)

    def hmget(self, key, fields):
        """
        获取缓存返回一个list
        :param key:
        :param fields:
        :return:
        """
        return self._client.hmget(key, fields)

    def hmget2dict(self, key, fields):
        """
        获取缓存返回一个list
        :param key:
        :param fields:
        :return:
        """
        cache_list = self._client.hmget(key, fields)
        return dict(zip(fields, cache_list))

    def hset(self, key, field, value, ex=10):
        pipe = self._client.pipeline()
        pipe.hset(key, field, value)
        if ex:
            pipe.expire(key, int(ex))
        return pipe.execute()

    def hmset(self, key, values, ex=10):
        assert isinstance(values, dict)
        pipe = self._client.pipeline()
        pipe.hmset(key, values)
        if ex:
            pipe.expire(key, int(ex))
        return pipe.execute()

    def hexists(self, key, field):
        return self._client.hexists(key, field)

    def hdel(self, key, field):
        return self._client.hdel(key, field)

    def hmdel(self, key, fields):
        assert isinstance(fields, list)
        return self._client.hdel(key, *fields)

    def hkeys(self, key):
        return self._client.hkeys(key)

    def hvals(self, key):
        return self._client.hvals(key)

    def hgetall(self, key):
        return self._client.hgetall(key)

    def get_json(self, key):
        """
        # 针对dict类型的数据的优化存储
        :param key:
        :return:
        """
        data = self._client.hgetall(key)
        if data:
            return {key: json.loads(val) for key, val in dict(data).iteritems()}
        return {}

    def set_json(self, key, value, ex=10):
        """
        @desc 针对dict类型数据的优化设置
        :param key:
        :param value:
        :param ex:
        :return:
        """
        assert isinstance(value, dict)
        cache_data = {k: json.dumps(v) for k, v in value.iteritems()}
        pipe = self._client.pipeline()
        pipe.hmset(key, cache_data)
        if ex:
            pipe.expire(key, int(ex))
        return pipe.execute()

    def delete_memoize(self, key, cache_per="", *args, **kwargs):
        cache_key = cache_per + key + ":" + self._make_cache_key(*args, **kwargs)
        self._client.delete(cache_key)

    def safe_delete_memoize(self, key, cache_per="", *args, **kwargs):
        """
        # 设置失效时间为-1，使得其自动失效
        """
        cache_key = cache_per + key + ":" + self._make_cache_key(*args, **kwargs)
        self._client.expire(cache_key, -1)

    def memoize(self, key, ex=30, cache_per="", is_json=False, is_update=False):
        """
        @desc 对有参函数结果进行缓存的装饰器,
        如果缓存获取异常，直接缓存穿透
        :param key: 缓存键
        :param ex: 失效时间
        :param is_json: 是否需要json优化
        :param is_update: 是否获取的同时更新缓存
        :param cache_per: 缓存前缀
        :return:
        """

        def memoize(f):
            @functools.wraps(f)
            def decorated_function(*args, **kwargs):
                cache_key = cache_per + key + ":" + self._make_cache_key(*args, **kwargs)
                get_func = self.get_json if is_json else self.get
                set_func = self.set_json if is_json else self.set
                try:
                    cache_data = get_func(cache_key)
                    if is_update:
                        set_func(cache_key, cache_data, ex=ex)
                    if cache_data:
                        return cache_data
                except Exception as e:
                    logger.error(e)
                    return f(*args, **kwargs)
                cache_data = f(*args, **kwargs)
                set_func(cache_key, cache_data, ex=ex)
                return cache_data

            return decorated_function

        return memoize

    def _make_cache_key(self, *args, **kwargs):
        """
        @desc 生成缓存识别键
        :param f:
        :param args:
        :param kwargs:
        :return:
        """
        l_data = filter(lambda x: x.__class__ in {str, int} and x, list(args) + list(kwargs.values()))
        value = ":" + ",".join(sorted([str(i).replace("-", "") for i in l_data]))
        m = hashlib.md5()
        m.update(value)
        return m.hexdigest()


class SimpleCache(object):
    """只针对字符串和json的缓存上下文管理器"""

    def __init__(self, key, ex=30, cache=None, is_json=False, is_update=True, is_except=True):
        self._key = key
        self._expire = ex
        self._client = cache
        self.data = None
        self.json = is_json
        self.update = is_update
        self._except = is_except

    def __enter__(self):
        self.data = self.get()
        if self.data:
            self.update = False
        return self

    def __exit__(self, exc_type, exc_val, exc_tb):
        if self.update and self.data:
            self.set()
        if not self._except:
            return True

    def get(self):
        if self.json:
            data = self._client.hgetall(self._key)
            if data:
                return {key: json.loads(val) for key, val in dict(data).iteritems()}
            return {}
        else:
            return self._client.get(self._key)

    def set(self):
        if self.json:
            assert isinstance(self.data, dict)
            cache_data = {k: json.dumps(v) for k, v in self.data.iteritems()}
            pipe = self._client.pipeline()
            pipe.hmset(self._key, cache_data)
            pipe.expire(self._key, int(self._expire))
            return pipe.execute()[0]
        else:
            return self._client.set(self._key, self.data, ex=self._expire)

    def exists(self):
        return self._client.exists(self._key)